package com.net.core.core.sql;

import com.net.core.core.YXException;
import org.springframework.core.annotation.AnnotationUtils;

import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

public class LambdaUtils {

    private static final Map<Class<?>, WeakReference<SerializedLambda>> FUNC_CACHE = new ConcurrentHashMap<>();

    public static <T> String getColumn(Function<T, ?> func) {
        SerializedLambda lambda = LambdaUtils.resolve(func);
        String fieldName = methodToProperty(lambda.getImplMethodName());
        Class<?> aClass = lambda.getInstantiatedType();
        Field field;
        try {
            field = aClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            fieldName = methodToProperty1(lambda.getImplMethodName());
            try {
                field = aClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException noSuchFieldException) {
                throw new YXException(e.getMessage(), e);
            }
        }
        MyTableField columnFiled = getColumnFiled(field);
        return columnFiled.getName();
    }

    public static MyTableField getColumnFiled(Field field) {
        TableField tableFieldAnno = AnnotationUtils.findAnnotation(field, TableField.class);
        MyTableField myTableField = new MyTableField();
        String tableName = field.getName();
        if (tableFieldAnno != null && tableFieldAnno.exist() && tableFieldAnno.value().trim().length() > 0) {
            tableName = tableFieldAnno.value().trim();
        } else {
            TableId tableIdAnno = AnnotationUtils.findAnnotation(field, TableId.class);
            if (tableIdAnno != null && tableIdAnno.value().trim().length() > 0) {
                tableName = tableIdAnno.value().trim();
            }
        }
        myTableField.setName(tableName);
        myTableField.setType(field.getType().getName());
        return myTableField;
    }

    public static String methodToProperty1(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        } else {
            throw new YXException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
        }
        return name;
    }

    public static String methodToProperty(String name) {
        name = methodToProperty1(name);
        if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }
        return name;
    }

    public static <T> SerializedLambda resolve(Function<T, ?> func) {
        Class<?> clazz = func.getClass();
        return Optional.ofNullable(FUNC_CACHE.get(clazz))
                .map(WeakReference::get)
                .orElseGet(() -> {
                    SerializedLambda lambda = resolve1(func);
                    FUNC_CACHE.put(clazz, new WeakReference<>(lambda));
                    return lambda;
                });
    }

    public static SerializedLambda resolve1(Function<?, ?> lambda) {
        if (!lambda.getClass().isSynthetic()) {
            throw new YXException("该方法仅能传入 lambda 表达式产生的合成类");
        }
        try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(serialize(lambda))) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
                Class<?> clazz = super.resolveClass(objectStreamClass);
                return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
            }
        }) {
            return (SerializedLambda) objIn.readObject();
        } catch (ClassNotFoundException | IOException e) {
            throw new YXException("This is impossible to happen", e);
        }
    }

    public static byte[] serialize(Object object) {
        if (object == null) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(object);
            oos.flush();
        } catch (IOException ex) {
            throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
        }
        return baos.toByteArray();
    }

}
